package cn.virens.web.components.shiro.oauth2;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import cn.virens.oauth2.Oauth2Client;
import cn.virens.oauth2.exception.Oauth2Exception;
import cn.virens.oauth2.standard.Oauth2AccessTokenRequest;
import cn.virens.util.Assert;
import cn.virens.web.components.shiro.ShiroAuthInterface;
import cn.virens.web.components.shiro.ShiroRealmInterface;
import cn.virens.web.components.shiro.oauth2.ex.Oauth2AuthenticationException;

public class Oauth2AuthorizingRealm extends AuthorizingRealm implements ShiroRealmInterface {
	private Logger logger = LoggerFactory.getLogger(Oauth2AuthorizingRealm.class);

	private Oauth2Client oauth2Client;
	private PrincipalGetter principalGetter;

	@Autowired
	@Qualifier(ShiroAuthInterface.NAME)
	private ShiroAuthInterface shiroAuthInterface;

	public Oauth2AuthorizingRealm() {
		this(null);
	}

	public Oauth2AuthorizingRealm(CacheManager cacheManager) {
		super(cacheManager, (a, b) -> true);
	}

	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof Oauth2AuthenticationToken;
	}

	public Oauth2Client getOauth2Client() {
		return oauth2Client;
	}

	public void setOauth2Client(Oauth2Client oauth2Client) {
		this.oauth2Client = oauth2Client;
	}

	public PrincipalGetter getPrincipalGetter() {
		return principalGetter;
	}

	public void setPrincipalGetter(PrincipalGetter principalGetter) {
		this.principalGetter = principalGetter;
	}

	@Override
	public void clearAuthorizationInfo(PrincipalCollection principals) {
		this.clearCachedAuthorizationInfo(principals);
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		logger.debug("执行授权信息...");

		String account = (String) getAvailablePrincipal(principals);
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

		authorizationInfo.addRoles(shiroAuthInterface.getRoles(account));// 获取用户角色列表
		authorizationInfo.addStringPermissions(shiroAuthInterface.getResources(account)); // 获取用户权限列表

		return authorizationInfo;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		logger.debug("执行认证信息...");

		Assert.isNull(principalGetter, "Oauth2PrincipalGetter is null");
		Assert.isNull(oauth2Client, "Oauth2RequestClient is null");

		Oauth2AuthenticationToken authToken = (Oauth2AuthenticationToken) token;
		Oauth2PrincipalCollection principals = new Oauth2PrincipalCollection();
		try {

			// 构建AssessToken请求
			Oauth2AccessTokenRequest request = oauth2Client.accessTokenRequest();
			request.setRedirectUri(authToken.getRedirectUri());
			request.setCode(authToken.getCode());
			request.doRequest((respone) -> {
				Object principal = principalGetter.getPrincipal(respone);

				// 将TokenInfo存了 认证信息 中
				principals.setRefreshToken(respone.getRefreshToken());
				principals.setAccessToken(respone.getAccessToken());
				principals.setExpiresIn(respone.getExpiresIn());
				principals.setAuthState(authToken.getState());
				principals.setAuthCode(authToken.getCode());
				principals.add(principal, getName());

			});

			return new Oauth2AuthenticationInfo(principals);
		} catch (Oauth2Exception e) {
			throw new Oauth2AuthenticationException(e);
		}
	}

	/**
	 * 不要进行验证了
	 */
	@Override
	protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {

	}

	@Override
	protected Object getAuthenticationCacheKey(AuthenticationToken token) {
		return token("shrio:authentication:", token);
	}

	@Override
	protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
		if (principals instanceof Oauth2PrincipalCollection) {
			return object("shrio:authentication:", ((Oauth2PrincipalCollection) principals).getAuthCode());
		} else {
			return object("shrio:authentication:", super.getAvailablePrincipal(principals));
		}

	}

	@Override
	protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
		return object("shrio:authorization:", principals);
	}

	private String token(String pref, AuthenticationToken token) {
		return (token == null ? null : (pref + token.getPrincipal()));
	}

	private String object(String pref, Object obj) {
		return (obj == null ? null : (pref + obj.toString()));
	}
}
